﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ArithmeticLibrary.Tree;

namespace ArithmeticLibrary.Visitors
{
    /// <summary>
    /// Visitor pro zjednoduseni a kraceni v aritmetickem stromu.
    /// </summary>
    class ReducerVisitor : IExpressionVisitor<Node, NoContext>
    {
        public Node Visit(NumberNode node, NoContext context)
        {
            return node;
        }

        public Node Visit(VariableNode node, NoContext context)
        {
            return node;
        }

        public Node Visit(PlusNode node, NoContext context)
        {
            Node left = node.Left.Accept(this);
            Node right = node.Right.Accept(this);

            // jestli je levy scitanec 0
            if (IsZero(left))
                // pak vratime jen pravy scitanec
                return right;
            
            // jestli je pravy scitanec 0
            if (IsZero(right))
                // pak vratime jen levy scitanec
                return left;
            
            // jinak mame nenulove scitance
            return new PlusNode(left, right);
        }

        public Node Visit(MinusNode node, NoContext context)
        {
            Node left = node.Left.Accept(this);
            Node right = node.Right.Accept(this);

            // jestli je levy oeprand 0
            if (IsZero(left))
                // pak vratime jen pravy scitanec s minusem
                return new UnaryMinusNode(right);
            
            // jestli je pravy operand 0
            if (IsZero(right))
                // pak vratime jen levy scitanec
                return left;

            // jinak mame nenulove scitance
            return new MinusNode(left, right);
        }

        public Node Visit(MultiplyNode node, NoContext context)
        {
            Node left = node.Left.Accept(this);
            Node right = node.Right.Accept(this);

            // jestli je jeden z cinitelu 0
            if (IsZero(left) || IsZero(right))
                // pak je cely soucin roven 0
                return new NumberNode(0);

            // jestli je levy cinitel 1
            if (IsOne(left))
                // vratime jen pravy cinitel
                return right;

            // jestli je pravy cinitel 1
            if (IsOne(right))
                // vratime jen levy cinitel
                return left;

            // jinak jsou oba cinitele nenulove a nejednickove
            return new MultiplyNode(left, right);
        }

        public Node Visit(DivideNode node, NoContext context)
        {
            Node left = node.Left.Accept(this);
            Node right = node.Right.Accept(this);

            if (IsZero(left))
                return new NumberNode(0);

            if (IsOne(right))
                return left;

            if (IsZero(right))
                return null;

            return new DivideNode(left, right);
        }

        public Node Visit(PowerNode node, NoContext context)
        {
            Node left = node.Left.Accept(this);
            Node right = node.Right.Accept(this);

            if (IsZero(right))
                return new NumberNode(1);

            return new PowerNode(left, right);
        }

        public Node Visit(UnaryMinusNode node, NoContext context)
        {
            Node child = node.Child.Accept(this);

            if (IsZero(child))
                return new NumberNode(0);

            if (IsNegative(child))
            {
                NumberNode n = (NumberNode)child;

                return new NumberNode(-n.Value);
            }

            return new UnaryMinusNode(child);
        }

        public Node Visit(SineNode node, NoContext context)
        {
            Node child = node.Child.Accept(this);

            if (child is NumberNode)
            {
                NumberNode n = (NumberNode)child;

                if (n.Value % Math.PI == 0)
                    return new NumberNode(0);
            }

            return new SineNode(child);
        }

        public Node Visit(CosineNode node, NoContext context)
        {
            Node child = node.Child.Accept(this);

            if (child is NumberNode)
            {
                NumberNode n = (NumberNode)child;

                if (n.Value % Math.PI == (Math.PI / 2))
                    return new NumberNode(0);
            }

            return new CosineNode(child);
        }

        public Node Visit(TangentNode node, NoContext context)
        {
            Node child = node.Child.Accept(this);

            if (child is NumberNode)
            {
                NumberNode n = (NumberNode)child;

                if (n.Value % Math.PI == 0)
                    return new NumberNode(0);
            }

            return new TangentNode(child);
        }

        public Node Visit(LogarithmNode node, NoContext context)
        {
            Node child = node.Child.Accept(this);

            if (IsOne(child))
                return new NumberNode(0);

            return new LogarithmNode(child);
        }

        public Node Visit(ExponentialNode node, NoContext context)
        {
            Node child = node.Child.Accept(this);

            if (IsZero(child))
                return new NumberNode(1);

            return new ExponentialNode(child);
        }

        public Node ReduceExpressionTree(Node node)
        {
            return node.Accept(this);
        }

        #region Internal Methods

        /// <summary>
        /// Zjisti, jestli je uzel cislo rovne 0.
        /// </summary>
        /// <param name="node">Uzel k vyhodnoceni.</param>
        /// <returns>Vrati TRUE, pokud je uzel cislo rovne 0.</returns>
        private bool IsZero(Node node)
        {
            if (node is NumberNode)
            {
                NumberNode n = (NumberNode)node;

                if (n.Value == 0) return true;
                else return false;
            }

            return false;
        }

        /// <summary>
        /// Zjisti, zda uzel je cislo rovne 1.
        /// </summary>
        /// <param name="node">Uzel k vyhodnoceni.</param>
        /// <returns>Vrati TRUE, pokud je uzel cislo rovne 1.</returns>
        private bool IsOne(Node node)
        {
            if (node is NumberNode)
            {
                NumberNode n = (NumberNode)node;

                if (n.Value == 1) return true;
                else return false;
            }

            return false;
        }

        /// <summary>
        /// Zjisti, zda je uzel zaporne cislo.
        /// </summary>
        /// <param name="node">Uzel k porovnani</param>
        /// <returns>Vrati TRUE, pokud je uzel zaporne cislo.</returns>
        private bool IsNegative(Node node)
        {
            if (node is NumberNode)
            {
                NumberNode n = (NumberNode)node;

                if (n.Value < 0) return true;
                else return false;
            }

            return false;
        }

        #endregion
    }
}
